/*
* Advanced C# messenger by Ilya Suzdalnitski. V1.0
*
* Based on Rod Hyde's "CSharpMessenger" and Magnus Wolffelt's "CSharpMessenger Extended".
*
* Features:
* Prevents a MissingReferenceException because of a reference to a destroyed message handler.
* Option to log all messages
* Extensive error detection, preventing silent bugs
*
* Usage examples:
1. Messenger.AddListener<GameObject>("prop collected", PropCollected);
Messenger.Broadcast<GameObject>("prop collected", prop);
2. Messenger.AddListener<float>("speed changed", SpeedChanged);
Messenger.Broadcast<float>("speed changed", 0.5f);
*
* Messenger cleans up its evenTable automatically upon loading of a new level.
*
* Don't forget that the messages that should survive the cleanup, should be marked with Messenger.MarkAsPermanent(string)
*
*/

//#define LOG_ALL_MESSAGES
//#define LOG_ADD_LISTENER
//#define LOG_BROADCAST_MESSAGE
#define REQUIRE_LISTENER

using System;
using System.Collections.Generic;

namespace Core
{
    public static class Messenger
    {
        #region Internal variables

        private static Dictionary<string, Delegate> eventTable = new();

        //Message handlers that should never be removed, regardless of calling Cleanup
        private static HashSet<string> permanentMessages = new();

        #endregion

        #region Helper methods
        //Marks a certain message as permanent.
        public static void MarkAsPermanent(string eventType)
        {
            permanentMessages.Add(eventType);
        }

        public static void Cleanup()
        {
            List<string> messagesToRemove = new List<string>();

            foreach (var pair in eventTable)
            {
                bool wasFound = permanentMessages.Contains(pair.Key);

                if (!wasFound)
                {
                    messagesToRemove.Add(pair.Key);
                }
            }

            foreach (string message in messagesToRemove)
            {
                eventTable.Remove(message);
            }
        }

        public static void PrintEventTable()
        {
            log.d("\t\t\t=== MESSENGER PrintEventTable ===");

            foreach (KeyValuePair<string, Delegate> pair in eventTable)
            {
                log.d("\t\t\t" + pair.Key + "\t\t" + pair.Value);
            }

            log.d("\n");
        }
        #endregion

        #region Message logging and exception throwing
        private static void OnListenerAdding(string eventType, Delegate listener)
        {
            if (!eventTable.ContainsKey(eventType))
            {
                eventTable.Add(eventType, null);
            }
#if DEBUG
            Delegate d = eventTable[eventType];
            if (d != null && d.GetType() != listener.GetType())
            {
                log.err("有相同Key但不相同类型的监听添加->>>" + eventType);
            }
#endif
        }

        private static void OnListenerRemoving(string eventType, Delegate listener)
        {
            if (eventTable.ContainsKey(eventType))
            {
                Delegate d = eventTable[eventType];
#if DEBUG
                if (d.GetType() != listener.GetType())
                {
                    log.err("有相同Key但不相同类型的监听删除->>>" + eventType);
                }
#endif
            }
            else
            {
                log.err("No Target Listener->>>" + eventType);
                eventTable.Add(eventType, null);
            }
        }

        private static void OnListenerRemoved(string eventType)
        {
            if (eventTable[eventType] == null)
            {
                eventTable.Remove(eventType);
            }
        }

        #endregion

        #region AddListener
        public static void AddListener(string eventType, Action handler)
        {
            OnListenerAdding(eventType, handler);
            eventTable[eventType] = (Action)eventTable[eventType] + handler;
        }

        public static void AddListener<T>(string eventType, Action<T> handler)
        {
            OnListenerAdding(eventType, handler);
            eventTable[eventType] = (Action<T>)eventTable[eventType] + handler;
        }

        public static void AddListener<T, U>(string eventType, Action<T, U> handler)
        {
            OnListenerAdding(eventType, handler);
            eventTable[eventType] = (Action<T, U>)eventTable[eventType] + handler;
        }

        public static void AddListener<T, U, V>(string eventType, Action<T, U, V> handler)
        {
            OnListenerAdding(eventType, handler);
            eventTable[eventType] = (Action<T, U, V>)eventTable[eventType] + handler;
        }

        public static void AddListener(Dictionary<string, Delegate> event_list)
        {
            foreach (var pair in event_list)
            {
                OnListenerAdding(pair.Key, pair.Value);
                eventTable[pair.Key] = Delegate.Combine(eventTable[pair.Key], pair.Value);
            }
        }
        #endregion

        #region RemoveListener
        public static void RemoveListener(string eventType, Action handler)
        {
            OnListenerRemoving(eventType, handler);
            eventTable[eventType] = (Action)eventTable[eventType] - handler;
            OnListenerRemoved(eventType);
        }

        public static void RemoveListener<T>(string eventType, Action<T> handler)
        {
            OnListenerRemoving(eventType, handler);
            eventTable[eventType] = (Action<T>)eventTable[eventType] - handler;
            OnListenerRemoved(eventType);
        }

        public static void RemoveListener<T, U>(string eventType, Action<T, U> handler)
        {
            OnListenerRemoving(eventType, handler);
            eventTable[eventType] = (Action<T, U>)eventTable[eventType] - handler;
            OnListenerRemoved(eventType);
        }

        public static void RemoveListener<T, U, V>(string eventType, Action<T, U, V> handler)
        {
            OnListenerRemoving(eventType, handler);
            eventTable[eventType] = (Action<T, U, V>)eventTable[eventType] - handler;
            OnListenerRemoved(eventType);
        }

        public static void RemoveListener(Dictionary<string, Delegate> event_list)
        {
            foreach (var pair in event_list)
            {
                OnListenerRemoving(pair.Key, pair.Value);
                eventTable[pair.Key] = Delegate.Remove(eventTable[pair.Key], pair.Value);
                OnListenerRemoved(pair.Key);
            }
        }
        #endregion

        #region Broadcast
        public static void Post(string eventType)
        {
            if (eventTable.TryGetValue(eventType, out var d))
            {
                (d as Action)?.Invoke();
            }
        }

        public static void Post<T>(string eventType, T t)
        {
            if (eventTable.TryGetValue(eventType, out var d))
            {
                (d as Action<T>)?.Invoke(t);
            }
        }

        public static void Post<T, U>(string eventType, T t, U u)
        {
            if (eventTable.TryGetValue(eventType, out var d))
            {
                (d as Action<T, U>)?.Invoke(t, u);
            }
        }

        public static void Post<T, U, V>(string eventType, T t, U u, V v)
        {
            if (eventTable.TryGetValue(eventType, out var d))
            {
                (d as Action<T, U, V>)?.Invoke(t, u, v);
            }
        }
        #endregion

        #region Unicast
        /// <summary>
        /// 向entity抛出事件
        /// </summary>
        public static void Post(Entity entity, string eventType)
        {
            entity?.Get<EventComponent>()?.Post(eventType);
        }

        /// <summary>
        /// 向entity抛出事件
        /// </summary>
        public static void Post<T>(Entity entity, string eventType, T t)
        {
            entity?.Get<EventComponent>()?.Post(eventType, t);
        }

        /// <summary>
        /// 向entity抛出事件
        /// </summary>
        public static void Post<T, U>(Entity entity, string eventType, T t, U u)
        {
            entity?.Get<EventComponent>()?.Post(eventType, t, u);
        }

        /// <summary>
        /// 向entity抛出事件
        /// </summary>
        public static void Post<T, U, V>(Entity entity, string eventType, T t, U u, V v)
        {
            entity?.Get<EventComponent>()?.Post(eventType, t, u, v);
        }
        #endregion

        #region Execute
        /// <summary>
        /// 向entity抛出事件
        /// </summary>
        public static TResult Execute<TResult>(Entity entity, string eventType)
        {
            var cc = entity?.Get<EventComponent>();
            if (cc == null) return default;
            return cc.Execute<TResult>(eventType);
        }

        /// <summary>
        /// 向entity抛出事件
        /// </summary>
        public static TResult Execute<T, TResult>(Entity entity, string eventType, T t)
        {
            var cc = entity?.Get<EventComponent>();
            if (cc == null) return default;
            return cc.Execute<T, TResult>(eventType, t);
        }

        /// <summary>
        /// 向entity抛出事件
        /// </summary>
        public static TResult Execute<T, U, TResult>(Entity entity, string eventType, T t, U u)
        {
            var cc = entity?.Get<EventComponent>();
            if (cc == null) return default;
            return cc.Execute<T, U, TResult>(eventType, t, u);
        }

        /// <summary>
        /// 向entity抛出事件
        /// </summary>
        public static TResult Execute<T, U, V, TResult>(Entity entity, string eventType, T t, U u, V v)
        {
            var cc = entity?.Get<EventComponent>();
            if (cc == null) return default;
            return cc.Execute<T, U, V, TResult>(eventType, t, u, v);
        }
        #endregion
    }
}
